package cn.ywang.spring.framework.aop.intercept;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author BeatificWang
 * @date 2019/5/4 21:44
 */
public class MethodInvocation implements Joinpoint {

	protected final Object proxy;

	protected final Object target;

	protected final Method method;

	protected Object[] arguments = new Object[0];

	private final Class<?> targetClass;

	/**
	 * Lazily initialized map of user-specific attributes for this invocation.
	 */
	private Map<String, Object> userAttributes;

	/**
	 * List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
	 * that need dynamic checks.
	 */
	protected final List<?> interceptorsAndDynamicMethodMatchers;

	/**
	 * Index from 0 of the current interceptor we're invoking.
	 * -1 until we invoke: then the current interceptor.
	 */
	private int currentInterceptorIndex = -1;

	/**
	 * Construct a new ReflectiveMethodInvocation with the given arguments.
	 *
	 * @param proxy                                the proxy object that the invocation was made on
	 * @param target                               the target object to invoke
	 * @param method                               the method to invoke
	 * @param arguments                            the arguments to invoke the method with
	 * @param targetClass                          the target class, for MethodMatcher invocations
	 * @param interceptorsAndDynamicMethodMatchers interceptors that should be applied,
	 *                                             along with any InterceptorAndDynamicMethodMatchers that need evaluation at runtime.
	 *                                             MethodMatchers included in this struct must already have been found to have matched
	 *                                             as far as was possibly statically. Passing an array might be about 10% faster,
	 *                                             but would complicate the code. And it would work only for static pointcuts.
	 */
	public MethodInvocation(
			Object proxy, Object target, Method method, Object[] arguments,
			Class<?> targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
		this.proxy = proxy;
		this.target = target;
		this.targetClass = targetClass;
		this.method = method;
		this.arguments = arguments;
		this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
	}

	@Override
	public Object proceed() throws Throwable {
		//如果Interceptor执行完了，则执行joinPoint
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return this.method.invoke(this.target, this.arguments);
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		//如果要动态匹配joinPoint
		if (interceptorOrInterceptionAdvice instanceof MethodInterceptor) {
			MethodInterceptor mi =
					(MethodInterceptor) interceptorOrInterceptionAdvice;
			return mi.invoke(this);
		}
		else {
			//动态匹配失败时,略过当前Interceptor,调用下一个Interceptor
			return this.proceed();
		}
	}

	@Override
	public Object getThis() {
		return this.target;
	}

	@Override
	public AccessibleObject getStaticPart() {
		return null;
	}

	@Override
	public Object[] getArguments() {
		return this.arguments;
	}

	@Override
	public Method getMethod() {
		return this.method;
	}

	@Override
	public void setUserAttribute(String key, Object value) {
		if (value != null) {
			if (this.userAttributes == null) {
				this.userAttributes = new HashMap<>(2);
			}
			this.userAttributes.put(key, value);
		}
		else {
			if (this.userAttributes != null) {
				this.userAttributes.remove(key);
			}
		}
	}

	@Override
	public Object getUserAttribute(String key) {
		return (this.userAttributes != null ? this.userAttributes.get(key) : null);
	}

}
